# -*- coding: utf-8 -*-

import time
import sys

from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (QApplication, QPushButton, QTextEdit,
                             QVBoxLayout, QWidget)

# when app raises uncaught exception, print info
def trap_exc_during_debug(*args):
    print(args)
sys.excepthook = trap_exc_during_debug


class Worker(QObject):

    sig_working = pyqtSignal(int, str)
    sig_finished = pyqtSignal(int)
    sig_output = pyqtSignal(str)

    def __init__(self, id: int):
        super().__init__()
        self.__id = id
        self.__abort = False

    @pyqtSlot()
    def work(self):

        thread_name = QThread.currentThread().objectName()
        thread_id = int(QThread.currentThreadId())
        self.sig_output.emit(
            '  running worker #{} from {}(#{})'.format(self.__id,
                                                       thread_name,
                                                       thread_id))
        for step in range(10):
            time.sleep(5)
            self.sig_working.emit(self.__id, 'step ' + str(step))
            app.processEvents()
            if self.__abort:
                self.sig_output.emit(
                    '  worker #{} aborting work at step {}'.format(self.__id,
                                                                   step))
                break
        self.sig_finished.emit(self.__id)

    def abort(self):
        self.sig_output.emit(
            '  worker #{} notified to abort'.format(self.__id))
        self.__abort = True


class MyWidget(QWidget):

    NUM_THREADS = 3
    sig_abort_workers = pyqtSignal()

    def __init__(self):

        super().__init__()
        self.setWindowTitle("Thread Example")
        form_layout = QVBoxLayout()
        self.setLayout(form_layout)
        self.resize(400, 800)

        self.btn_start = QPushButton()
        self.btn_start.clicked.connect(self.start_workers)
        self.btn_start.setText("Start {} threads".format(self.NUM_THREADS))
        form_layout.addWidget(self.btn_start)
        self.btn_stop = QPushButton()
        self.btn_stop.clicked.connect(self.abort_workers)
        self.btn_stop.setText("Stop threads")
        self.btn_stop.setDisabled(True)
        form_layout.addWidget(self.btn_stop)
        self.txt_log = QTextEdit()
        form_layout.addWidget(self.txt_log)
        self.txt_prg = QTextEdit()
        form_layout.addWidget(self.txt_prg)

        QThread.currentThread().setObjectName('main')
        self.__workers_done = None
        self.__threads = None

    def start_workers(self):

        self.txt_log.append('Starting {} threads'.format(self.NUM_THREADS))
        self.btn_start.setDisabled(True)
        self.btn_stop.setEnabled(True)

        self.__workers_done = 0
        self.__threads = []
        for idx in range(self.NUM_THREADS):
            worker = Worker(idx)
            thread = QThread()
            thread.setObjectName('thread_' + str(idx))
            self.__threads.append((thread, worker))
            worker.moveToThread(thread)
            worker.sig_working.connect(self.on_worker_working)
            worker.sig_finished.connect(self.on_worker_finished)
            worker.sig_output.connect(self.txt_log.append)
            self.sig_abort_workers.connect(worker.abort)
            thread.started.connect(worker.work)
            thread.start()

    @pyqtSlot()
    def abort_workers(self):

        self.txt_log.append('Asking each worker to abort')
        self.sig_abort_workers.emit()

    @pyqtSlot(int, str)
    def on_worker_working(self, worker_id: int, data: str):

        # self.txt_log.append('Worker #{}: {}'.format(worker_id, data))
        self.txt_prg.append('worker #{}: {}'.format(worker_id, data))

    @pyqtSlot(int)
    def on_worker_finished(self, worker_id):

        self.txt_log.append('-- Worker #{} done'.format(worker_id))
        self.txt_prg.append('-- Worker #{} done'.format(worker_id))
        self.__workers_done += 1

        thread, worker = self.__threads[worker_id]
        thread.quit()
        thread.wait()

        if self.__workers_done == self.NUM_THREADS:
            self.txt_log.append('No more workers active')
            self.btn_start.setEnabled(True)
            self.btn_stop.setDisabled(True)
            self.txt_log.append('All threads exited')



if __name__ == "__main__":

    app = QApplication([])
    wdgt = MyWidget()
    wdgt.show()
    sys.exit(app.exec_())